# In main SConstruct (~/git/cpluginframework/SConstruct)
import os
import sys
import json # Needed to potentially load config if it were external
from SCons.Script import *

# --- Basic Environment Setup ---
env = Environment()

# --- Configuration ---
build_dir = 'build'
lib_dir = os.path.join(build_dir, 'lib') # Final library directory: build/lib
root_dir = GetLaunchDir()
plugins_base_dir = 'plugins' # Directory containing plugin subdirectories

# --- Compiler and Linker Flags ---
env.Append(CCFLAGS=['-g', '-fPIC'])
env.Append(CCFLAGS=[
    '-Wall', # Add warnings
    '-Wextra',
    '-DMPACK_READER_API=1',
    '-DMPACK_WRITER_API=1',
    '-DMPACK_NODE_API=1',
    '-DMPACK_STDIO=1'
])
env.Append(CPPDEFINES={'PROJECT_ROOT': '"' + root_dir + '"'})

# --- Include Paths ---
# Ensure MPack and Core headers are available globally and for plugins
core_include_paths = [
    'core', # For plugin.h
    '3rd'   # For mpack.h
]
env.Append(CPPPATH=core_include_paths)

# --- Library Paths and Libraries ---
# Linker search path *only includes the final lib directory*
env.Append(LIBPATH=[lib_dir]) # <--- Ensure this is build/lib
env.Append(LIBS=['dl']) # Base libs for main program
env.Append(LINKFLAGS=['-Wl,--no-as-needed']) # Often needed for dlopen dependencies

# --- Create Build Directories ---
print(f"Ensuring build directory exists: {build_dir}")
os.makedirs(build_dir, exist_ok=True)
print(f"Ensuring library directory exists: {lib_dir}")
os.makedirs(lib_dir, exist_ok=True)

# --- Build Dependencies List ---
program_depends = []
plugin_nodes = [] # Keep track of built plugin nodes

# -----------------------------------------------------------------------------
#  Build MPack Shared Library (Must be built before plugins that depend on it)
# -----------------------------------------------------------------------------
mpack_sconscript = os.path.join('3rd', 'mpack', 'SConstruct')
# MPack's intermediate object file directory (can be same as lib_dir for simplicity here)
mpack_variant_dir = os.path.join(build_dir, 'mpack_objs')
mpack_lib_node = None # Initialize library node

if os.path.exists(mpack_sconscript):
    print(f"INFO: Building MPack library objects in {mpack_variant_dir}, output to {lib_dir}")
    try:
        # Clone env to avoid side effects, export necessary paths
        mpack_env = env.Clone()
        mpack_lib_result = SConscript(
            mpack_sconscript,
            exports={'env': mpack_env, 'lib_dir': lib_dir, 'root_dir': root_dir},
            variant_dir=mpack_variant_dir,
            duplicate=0
        )

        if mpack_lib_result:
            # Assume MPack SConstruct returns the library node directly or in a list
            if isinstance(mpack_lib_result, list):
                mpack_lib_node = mpack_lib_result[0] if mpack_lib_result else None
            else:
                mpack_lib_node = mpack_lib_result

            if mpack_lib_node:
                print(f"INFO: MPack library node captured: {mpack_lib_node}")
                program_depends.append(mpack_lib_node) # Main program depends on libmpack.so
            else:
                 print("WARNING: MPack SConscript did not return a valid library node.")
                 # Exit(1) # Decide if fatal
        else:
            print("WARNING: MPack SConscript did not return a result.")
            # Exit(1) # Decide if fatal

    except Exception as e:
        print(f"ERROR: Failed to execute MPack SConscript: {e}")
        # Exit(1)
else:
    print(f"WARNING: No SConstruct found for MPack at: {mpack_sconscript}")
    # Exit(1)

# Ensure MPack build is attempted before proceeding
if not mpack_lib_node:
    print("ERROR: MPack library node not available. Cannot proceed to build plugins.")
    Exit(1)

# -----------------------------------------------------------------------------
#  Define Global Configuration (Used for config generation AND plugin discovery)
# -----------------------------------------------------------------------------
global_config = {
    "plugins": {
        "console_plugin": {
            "message": "Hello from console plugin config!", # Example specific config
            "verbose": True
        },
        "gui_plugin": { # Assuming this plugin exists or will be added
            "window_title": "My GUI Plugin",
            "width": 800
        },
        # "quic_plugin": { # Example - add if it exists
        #     "port": 4433,
        #     "cert_file": "path/to/cert.pem"
        # }
    }
    # Add other top-level config keys if needed
    # "log_level": "INFO"
}

# -----------------------------------------------------------------------------
#  Build Plugins Dynamically Based on global_config
# -----------------------------------------------------------------------------
print("-" * 40)
print("Building Plugins...")

plugin_names_to_build = list(global_config.get("plugins", {}).keys())

if not plugin_names_to_build:
    print("INFO: No plugins listed in global_config['plugins'].")
else:
    print(f"INFO: Found plugins to build in config: {plugin_names_to_build}")

    for plugin_name in plugin_names_to_build:
        plugin_dir = os.path.join(plugins_base_dir, plugin_name)
        plugin_sconscript = os.path.join(plugin_dir, 'SConstruct')
        plugin_variant_dir = os.path.join(build_dir, 'plugins', plugin_name) # Intermediate files

        print(f"  Checking for plugin: {plugin_name} at {plugin_dir}")

        if not os.path.isdir(plugin_dir):
            print(f"  WARNING: Plugin directory not found: {plugin_dir}. Skipping.")
            continue
        if not os.path.exists(plugin_sconscript):
            print(f"  WARNING: SConstruct not found for plugin {plugin_name} at: {plugin_sconscript}. Skipping.")
            continue

        print(f"  Building plugin: {plugin_name} (Objects in {plugin_variant_dir}, output to {lib_dir})")
        try:
            # Clone env, export necessary paths
            plugin_env = env.Clone()
            # Ensure plugin links against mpack (LIBS might be needed here or in plugin SConstruct)
            # plugin_env.Append(LIBS=['mpack']) # Add here if plugin SConstruct doesn't

            plugin_result = SConscript(
                plugin_sconscript,
                exports={'env': plugin_env,
                         'lib_dir': lib_dir,
                         'build_dir': build_dir, # Pass build_dir too if needed
                         'root_dir': root_dir,
                         'plugin_name': plugin_name # Pass the name if SConstruct needs it
                         },
                variant_dir=plugin_variant_dir,
                duplicate=0
            )

            # Assume plugin SConstruct returns the built library node
            plugin_node = None
            if plugin_result:
                 if isinstance(plugin_result, list):
                     plugin_node = plugin_result[0] if plugin_result else None
                 else:
                     plugin_node = plugin_result # Assume direct node return

            if plugin_node:
                print(f"  Successfully built plugin: {plugin_name} -> {plugin_node}")
                plugin_nodes.append(plugin_node)
                # Main program implicitly depends on plugins via dlopen,
                # but explicitly depending helps ensure build order if needed.
                program_depends.append(plugin_node)
            else:
                print(f"  WARNING: SConscript for plugin {plugin_name} did not return a valid node.")

        except Exception as e:
            print(f"  ERROR: Failed to build plugin {plugin_name}: {e}")
            # Decide whether to continue or exit
            # Exit(1)

print("Finished building plugins.")
print("-" * 40)

# -----------------------------------------------------------------------------
#  Generate Configuration File (Using the same global_config)
# -----------------------------------------------------------------------------

# Placeholder function - Implement actual MPack serialization here
def generate_config_action(target, source, env, config_data):
    """Generates the config.mp file using MessagePack serialization."""
    print(f"Generating configuration file: {target[0]}")
    # print(f"Config data to serialize: {config_data}") # Can be verbose

    try:
        # --- Use the standard 'msgpack' library ---
        import msgpack # <--- CHANGE HERE

        # Use packb to get bytes directly. use_bin_type=True is default
        # for newer versions but good practice to include for compatibility.
        packed_config = msgpack.packb(config_data, use_bin_type=True) # <--- CHANGE HERE
        print(f"  Serialized {len(packed_config)} bytes using msgpack.")

    except ImportError:
        print("ERROR: 'msgpack' library not found. Please install it (`pip install msgpack`).") # <--- CHANGE HERE
        print("       Cannot serialize config file.")
        return 1 # Indicate failure
    except Exception as e:
        print(f"ERROR: Failed to serialize config data using msgpack: {e}") # <--- CHANGE HERE
        return 1 # Indicate failure

    try:
        # Write as binary
        with open(str(target[0]), "wb") as f:
            f.write(packed_config)
    except IOError as e:
        print(f"ERROR: Could not write config file {target[0]}: {e}")
        return 1 # Indicate failure
    return 0 # Indicate success


config_mp_target = os.path.join(build_dir, 'config.mp') # Target in build dir
config_mp = env.Command(
    target=config_mp_target,
    source=[], # No source files, generated from data
    action=Action(lambda target, source, env: generate_config_action(target, source, env, global_config),
                  "Generating $TARGET from global_config")
)
program_depends.append(config_mp) # Program depends on the config file

# -----------------------------------------------------------------------------
#  Build Main Executable
# -----------------------------------------------------------------------------
core_src_files = Glob('core/*.c')
print("INFO: Core source files:", [str(f) for f in core_src_files])

program_target = os.path.join(build_dir, 'microgui')

# Define the program, link mpack (linker will find libmpack.so in LIBPATH=[build/lib])
program_env = env.Clone()
program_env.Append(LIBS=['mpack']) # Explicitly link main program against mpack

program = program_env.Program(
    target=program_target,
    source=core_src_files
    # LIBS=['mpack', 'dl'] # Already added to env or program_env
    # LIBPATH is already set in env
)

# --- Explicit Dependencies ---
# Ensure program depends on MPack lib, generated config, and all built plugins
print(f"INFO: Program '{program_target}' depends on: {[str(d) for d in program_depends]}")
Depends(program, program_depends)

# --- Default Target ---
Default(program)

# --- Alias for Plugins ---
# Useful for building only the plugins: scons plugins
Alias('plugins', plugin_nodes)

# --- Final Info ---
print("-" * 40)
print(f"Build directory: {os.path.abspath(build_dir)}")
print(f"Library directory: {os.path.abspath(lib_dir)}")
print(f"Program target: {program_target}")
print(f"Config target: {config_mp}")
print(f"Plugins built: {[str(p) for p in plugin_nodes]}")
print(f"Program depends on: {[str(d) for d in program_depends]}")
print("-" * 40)

